
#include <assert.h>
#include <stdlib.h>

#include "core/olisp_mem.h"
#include "utility/olisp_reader.h"
#include "utility/olisp_printer.h"
#include "utility/olisp_helpers.h"


/*
 * main reading function what is used to get symbols from certain source -
 * file stream or string and calculate position as well
 */
static int context_getc( reader_context * ctx )
{
    int c;

    if( ctx->ungetc )
    {
        c = ctx->ungetc;
        ctx->ungetc = 0;
    }
    else
    {
        c = fgetc( ctx->stream );

        if( c == '\n' )
        {
            ++ ctx->line;
            ctx->pos = 0;
        }
        else
            ++ ctx->pos;
    }

    return c;
}


static int isidstart_valid( int c )
{
    static int buf[256] = { 0 };
    static int initialized = 0;

    // initialize buffer
    if( !initialized )
    {
        int i;
        for( i = 0; i < 256; ++ i )
        {
            if(
                ((i >= 'a') && (i <= 'z')) ||
                ((i >= 'A') && (i <= 'Z')) ||
                ((i >= '0') && (i <= '9')) ||
                (i == '?') || (i == '!') || (i == '.') || (i == '%') ||
                (i == '$') || (i == '^') ||
                (i == '<') || (i == '>') || (i == '=') || (i == '~') ||
                (i == '+') || (i == '-') || (i == '*') || (i == '/')
                )
            {
                buf[i] = 1;
            }
        }

        initialized = 1;
    }
    
    return (c > 0 && c < 256) ? buf[c] : 0;
}

static int isidbody_valid( int c )
{
    return isidstart_valid( c );
}


// default print functions
static void default_message(
    interpreter_t * self, int severity,
    obj_t * location, const char * msg )
{}

static interpreter_vtbl_t default_error_handler_vtbl =
{
    0,
    &default_message,
    0,
    0
};

static interpreter_t default_error_handler =
{ &default_error_handler_vtbl };


/*
 * dump feedback support
 */

static void dump_message(
    interpreter_t * self, int severity,
    obj_t * location, const char * msg )
{
    FILE * stream = stderr;

    fputs( "reader", stream );

    if( location )
    {
        printer_context prn_ctx = { 0 };

        fputc( ' ', stream );
        prn_ctx.stream = stream;
        print_elem( location, &prn_ctx );
        fputc( ' ', stream );
    }

    fputs( ": ", stream );
    fputs( msg, stream );
    fputc( '\n', stream );
}

static interpreter_vtbl_t dump_error_handler_vtbl =
{
    0,
    &dump_message,
    0,
    0
};



#ifdef LISP_FEAT_QUOTE_SUPPORT
// default quote manager
static void default_quote_dispose( lisp_quote_mgr_t * self )
{}

static void default_set_quote_marker(
    lisp_quote_mgr_t * self,
    obj_t * elem,
    quote_type_t quote_marker
    )
{}

static quote_type_t default_get_quote_marker(
    lisp_quote_mgr_t * self,
    obj_t * elem
    )
{ return 0; }

static lisp_quote_mgr_vtbl_t default_quote_mgr_vtbl =
{
    &default_quote_dispose,
    &default_set_quote_marker,
    &default_get_quote_marker
};

static lisp_quote_mgr_t default_quote_mgr = { &default_quote_mgr_vtbl };
#endif // LISP_FEAT_QUOTE_SUPPORT


/*
 * lists stack definition
 */

typedef struct
{
    list_obj_t ** arr;
    size_t count;
    size_t allocated;
} lists_stack_t;

#define STACK_GROW_INREMENT     8

static list_obj_t * push_new_list( lists_stack_t * stack )
{
    list_obj_t * list;
    
    // realloc stack if needed
    if( stack->count + 1 > stack->allocated )
    {
        size_t new_alloc = stack->allocated + STACK_GROW_INREMENT;
        void * new_arr = lisp_realloc( stack->arr, new_alloc * sizeof(list_obj_t *) );
        if( new_arr == 0 )
        {
            return 0;
        }
        
        stack->arr = new_arr;
        stack->allocated = new_alloc;
    }

    list = create_list( 0 );
    if( list == 0 )
        return 0;

    stack->arr[stack->count++] = list;
    
    return list;
}

static list_obj_t * top_list( lists_stack_t * stack )
{
    return stack->arr[stack->count - 1];
}

// popped list should be released on the caller side
static void pop_list( lists_stack_t * stack )
{
    if( !stack->count )
        return;

    -- stack->count;
}

static void dispose_stack( lists_stack_t * stack )
{
    size_t n;
    for( n = 0; n < stack->count; ++n )
    {
        RELEASE( stack->arr[n] );
    }

    lisp_free( stack->arr );

    stack->arr = 0;
    stack->count = 0;
    stack->allocated = 0;
}

/*
 * parses string
 */
static obj_t * parse_string( reader_context * ctx, int * ch )
{
    // TODO: better string handling
    #define STR_BUF_SIZE 511
    char str_buf[STR_BUF_SIZE + 1];
    size_t len = 0;

    for( ;; )
    {
        int c = context_getc( ctx );
        if( c <= 0 )
        {
            // closing `"' hadn't met
            interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, "non-terminated string met" );
            return 0;
        }

        if( c == '"' )
        {
            *ch = context_getc( ctx );
            break;
        }

        str_buf[len ++] = (char)c;
        if( len > STR_BUF_SIZE )
        {
            interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, "string too big" );
            return 0;
        }
    }

    str_buf[len] = 0;
    return (obj_t *)create_string( str_buf );
}

/*
 * parses symbol and creates corresponding obj_t
 * last read symbol returns back in ch
 */
static obj_t * parse_symbol( reader_context * ctx, int * ch )
{
    int c = *ch;
    obj_t * e = 0;

    // determines number
    #define DIGIT_STYLE_INTEGER 1
    #define DIGIT_STYLE_DOUBLE  2
    int digit_style = DIGIT_STYLE_INTEGER;
    int floating_point = 0;

    if( c == '"' )
        return parse_string( ctx, ch );

    if( isidstart_valid(c) )
    {
        // TODO: better id handling
        #define ID_BUF_SIZE 255
        char id_buf[ID_BUF_SIZE + 1];
        size_t len = 0;

        do
        {
            if( len > ID_BUF_SIZE )
            {
                interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, "identifier string too big" );
                return 0;
            }

            // digit test
            if( (c < '0') || (c > '9') )
            {
                if( c == '.' && !floating_point )
                {
                    floating_point = 1;
                    digit_style = DIGIT_STYLE_DOUBLE;
                }
                else
                    digit_style = 0;
            }

            id_buf[len ++] = (char)c;
            c = context_getc( ctx );
        }
        while( isidbody_valid(c) );
        
        id_buf[len] = 0;

        if( !strcmp(id_buf, "nil") )
        {
            e = nil_elem;
        }
        else if( !strcmp(id_buf, "t") )
        {
            e = true_elem;
        }
        else if( digit_style == DIGIT_STYLE_INTEGER )
        {
            e = (obj_t *)create_int_number( atoi(id_buf) );
        }
        else if( digit_style == DIGIT_STYLE_DOUBLE )
        {
            e = (obj_t *)create_dbl_number( atof(id_buf) );
        }
        else
        {
            e = (obj_t *)create_identifier( id_buf );
        }

        *ch = c;
    }

    return e;
}

/*
 * skips whitespace with comments block
 * and returns first meaningful symbol
 */
static int read_skip_whitespace( reader_context * ctx )
{
    for( ;; )
    {
        int c = context_getc( ctx );

        if( c == ';' )
        {
            // skip single line comments
            do
            {
                c = context_getc( ctx );
            } while( c != '\n' && c > 0 );

            continue;
        }
        else if( c == '#' )
        {
            // reveal multiline comments
            int c2 = context_getc( ctx );
            if( c2 == '|' )
            {
                c = context_getc( ctx );
                c2 = context_getc( ctx );

                // skip multiline comments
                for( ;; )
                {
                    if( c == '|' && c2 == '#' )
                        break;

                    c = c2;
                    c2 = context_getc( ctx );
                    if( c2 <= 0 )
                        break;
                }

                continue;
            }
            else
            {
                // unget prev character and continue reading
                ctx->ungetc = c2;
            }
        }
        else if( c == ' ' || c == '\r' || c == '\n' || c == '\t' )
        {
            // skip whitespace symbols
            continue;
        }

        return c;
    }
}

obj_t * read_elem( reader_context * ctx )
{
    lists_stack_t lists_stack = { 0 };
    list_obj_t * cur_list = 0;
    obj_t * result = 0;

    // in case of non-null error handler use a default one
    if( !ctx->err_handler )
        ctx->err_handler = &default_error_handler;

#ifdef LISP_FEAT_QUOTE_SUPPORT
    if( !ctx->quote_mgr )
        ctx->quote_mgr = &default_quote_mgr;
#endif

    // reading loop
    for( ;; )
    {
        int c = read_skip_whitespace( ctx );
#ifdef LISP_FEAT_QUOTE_SUPPORT
        int quote_mark = QUOTE_NONE;
#endif

        // eof check
        if( c <= 0 )
        {
            if( lists_stack.count )
            {
                // TODO: add location info where the one opened brace resides
                interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, "missing close brace" );
            }

            goto LEnd;
        }

#ifdef LISP_FEAT_QUOTE_SUPPORT
        // TODO: what to do if met such characters twice?
        switch( c )
        {
        case '\'':
            quote_mark = QUOTE_STRIGHT;
            c = read_skip_whitespace( ctx );
            break;

        case '`':
            quote_mark = QUOTE_BACK;
            c = read_skip_whitespace( ctx );
            break;

        case '&':
            quote_mark = QUOTE_AMPERSAND;
            c = read_skip_whitespace( ctx );
            break;

        case ':':
            quote_mark = QUOTE_COLON;
            c = read_skip_whitespace( ctx );
            break;

        case ',':
            quote_mark = QUOTE_COMMA;
            c = context_getc( ctx );
            if( c == '@' )
                quote_mark = QUOTE_COMMA_AT;
            else
                ctx->ungetc = c;

            c = read_skip_whitespace( ctx );
            break;
        }
#endif

        // handle meaningful symbols
        switch( c )
        {
        case '(':
            cur_list = push_new_list( &lists_stack );
            if( !cur_list )
                goto LEnd;
#ifdef LISP_FEAT_QUOTE_SUPPORT
            quote_mgr_set_marker( ctx->quote_mgr, (obj_t *)cur_list, quote_mark );
#endif
            break;

        case ')':
            // check whether this brace does not match any open brace
            if( !lists_stack.count )
            {
                char buf[64];

                sprintf( buf, "superfluos brace at %d:%d", ctx->line, ctx->pos );
                interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, buf );
                goto LEnd;
            }
            else
            {
                // substitute zero-length list to nil on place
                obj_t * cur_list_elem;

                if( list_length(cur_list) )
                {
                    cur_list_elem = (obj_t *)cur_list;
                }
                else
                {
                    cur_list_elem = nil_elem;
#ifdef LISP_FEAT_QUOTE_SUPPORT
                    quote_mgr_set_marker( ctx->quote_mgr,
                        (obj_t *)cur_list, 0 );
#endif
                    RELEASE( cur_list );
                }

                pop_list( &lists_stack );

                if( lists_stack.count )
                {
                    // append this list to top-level list
                    list_obj_t * t = top_list( &lists_stack );
                    int res = list_append( t, cur_list_elem );
                    RELEASE( cur_list_elem );
                    if( !res )
                        goto LEnd;

                    cur_list = t;
                }
                else
                {
                    // this is a last element
                    result = cur_list_elem;
                    goto LEnd;
                }
            }
            break;

        default:
            // do parse identifiers/numbers/t/nil elements
            {
                obj_t * e = parse_symbol( ctx, &c );
                if( e )
                {
#ifdef LISP_FEAT_QUOTE_SUPPORT
                    quote_mgr_set_marker( ctx->quote_mgr,
                        (obj_t *)e, quote_mark );
#endif

                    if( !cur_list )
                    {
                        result = e;
                        goto LEnd;
                    }
                    else
                    {
                        int res = list_append( cur_list, e );
                        RELEASE( e );
                        if( !res )
                            goto LEnd;
                    }

                    ctx->ungetc = c;
                    continue;
                }
            }

            // unknown symbol passed
            {
                char buf[64];

                sprintf( buf, "unknown symbol `%c' found at %d:%d",
                    (char)c, ctx->line, ctx->pos );
                interpreter_message( ctx->err_handler, MSG_SEV_ERROR, 0, buf );
            }
            break;
        } // switch
    } // for( ;; )

LEnd:
    dispose_stack( &lists_stack );
    return result;
}
